/**
 * @Authors
 * Avinash Sridhar	G Number: G00861300
 * Martin Nguyen	G Number: G00396532
 * Jitin Krishnan	G Number: G00834757
 * 
 * @Course
 * CS571 Operating Systems Fall 2013
 * Assignment 3
 * Due: Dec 2, 2013 
 * Instructor: Hakan Aydin
 * 
 * Filename: TokenRequestHandler.java
 * 
 **/
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.sql.Timestamp;
import java.util.Date;

public class TokenRequestHandler extends Thread {

	private RMIServer mServer;
	
	private ServerSocket mTokenHandlerServerSocketPort;	//coming from the left neighbor

	public InetAddress server_addr = InetAddress.getByName("localhost");
	
	public TokenRequestHandler(RMIServer server) throws IOException
	{
		mServer = server;
		mTokenHandlerServerSocketPort = new ServerSocket(mServer.getmCurrDeptModule().mTokenSocketPortNumber);	//going to be listening on your Token socket port
		System.out.println("Listening for Tokens at port <" + mTokenHandlerServerSocketPort.getLocalPort() + ">...");
	}

	@Override
	public void run() 
	{
		while (true) {
			try{ 
				if (mServer.mReconfig)
				{
					WriteToPort("CON_L" + mServer.getmCurrDeptModule().toString(), mServer.getmRightDeptModule());	//tell Right to 'configure left'
					WriteToPort("CON_R" + mServer.getmCurrDeptModule().toString(), mServer.getmLeftDeptModule());	//tell Left to 'configure right'	
					mServer.mReconfig = false;
				}
				else if (mServer.getmInitialToken() &&
						 (mServer.getmLeftDeptModule().mName.equals(mServer.getmCurrDeptModule().mName)	||
						 mServer.getmRightDeptModule().mName.equals(mServer.getmCurrDeptModule().mName)))
				{
					try {
						System.out.println("-----CS Thread-----");
						sleep((long) mServer.ProcessingDelay);
						
						if (mServer.getmHasRequests())
						{
							//--------------CRITICAL SECTION-----------------
							System.out.println(mServer.getmCurrDeptModule().mName + " entering Critical section at " + new Timestamp(new Date().getTime()) + "*****");
							
							mServer.setmHasToken(true); 	//should awaken the sleeping main thread in either lookup or reserve
							
							sleep((long) mServer.CriticalSectionDelay);
							
							System.out.println(mServer.getmCurrDeptModule().mName + " exiting Critical section at " + new Timestamp(new Date().getTime()) + "*****");
							//--------------END CRITICAL SECTION-------------
							mServer.setmHasToken(false);
						}
				
						//pass off the token CLOCKWISE (TO THE RIGHT NEIGHBOR)
						//WriteToPort("TOKEN", mServer.getmRightDeptModule());

						System.out.println("--------------------");
					} catch (InterruptedException e)
					{
						
					}
					
				}
				else if (mServer.getmInitialToken() &&
						 !mServer.getmLeftDeptModule().mName.equals(mServer.getmCurrDeptModule().mName)	&&
						 !mServer.getmRightDeptModule().mName.equals(mServer.getmCurrDeptModule().mName))	//if you are the first server being initiated... you have the token > do your stuff > pass off token
				{
					mServer.setmBusy(true); //doing work
					
					//send START_TIMER to right neighbor...."ST*5* + left modules info"
					if (mServer.getmHasRequests())
						WriteToPort("ST:" + (mServer.CriticalSectionDelay + mServer.ProcessingDelay+1000) + ":" + mServer.getmCurrDeptModule().toString(), mServer.getmRightDeptModule());
					else
						WriteToPort("ST:" + (mServer.ProcessingDelay+1000) + ":" + mServer.getmCurrDeptModule().toString(), mServer.getmRightDeptModule());
	
					//wait for OK reply within ~ 0-1 seconds MAX
					System.out.println("Going to wait for 1 second starting " + System.currentTimeMillis());
					mTokenHandlerServerSocketPort.setSoTimeout(1000);
	
					String okay = "";
					try {
	
						okay = ReadFromSocket(mTokenHandlerServerSocketPort.accept()); //blocking call (this time will block MAX FOR 1000 ms)
	
					} catch (SocketTimeoutException e)
					{
						System.out.println("Detected that Right Neighbor must be dead at t=0 at " + System.currentTimeMillis());

						//set right = right_right
						mServer.setmRightDeptModule(mServer.getmRight_RightDeptModule());
	
						//Wait for fix.......
						synchronized (this)
						{
							System.out.println("fixing ring...");
							while (mServer.getmRight_RightDeptModule().mName == null)
							{
								try {
									wait();
								} catch (InterruptedException e1) {}
							}
							System.out.println("ring fixed at " + System.currentTimeMillis());
						}
						
						//after fix.........
						//send START_TIMER to NEW RIGHT neighbor...."ST*5* + left modules info"
						if(!mServer.getmLeftDeptModule().mName.equals(mServer.getmCurrDeptModule().mName)	&&
								 !mServer.getmRightDeptModule().mName.equals(mServer.getmCurrDeptModule().mName)){
						if (mServer.getmHasRequests())
							WriteToPort("ST:" + (mServer.CriticalSectionDelay + mServer.ProcessingDelay+1000) + ":" + mServer.getmCurrDeptModule().toString(), mServer.getmRightDeptModule());
						else
							WriteToPort("ST:" + (mServer.ProcessingDelay+1000) + ":" + mServer.getmCurrDeptModule().toString(), mServer.getmRightDeptModule());
	
						//wait for OK reply within ~ 0-1 seconds MAX
						mTokenHandlerServerSocketPort.setSoTimeout(1000);
	
						try {
							okay = ReadFromSocket(mTokenHandlerServerSocketPort.accept()); //blocking call (this time will block MAX FOR 1000 ms)
						} catch (SocketTimeoutException e2) { e2.printStackTrace(); }	//should get "OK"...two in a row broken can't happen
					}
					}
	
					if (okay.contains("OK")&& !okay.contains("TOKEN"))	//got "OK" from either Right --or-- Right_Right (new Right)
					{
						//update right_right with info provided
						String[] temp = okay.split("#");
						mServer.setmRight_RightDeptModule(new DeptModuleInfo(temp[1], Integer.parseInt(temp[2])));
												
						// new thread to do (Processing + C.S.)
						new CriticalSectionService(mServer).start();
												
						//wait for RECVD_TOKEN within ~ 5-6 seconds MAX
						//---TODO-----
						//wait for the right amount of time
						System.out.println("Going to wait for appropriate amount of time starting " + System.currentTimeMillis());
						
						//mTokenHandlerServerSocketPort.setSoTimeout(6000);
						//////////////////////////
						if(mServer.getmHasRequests())
							mTokenHandlerServerSocketPort.setSoTimeout(mServer.CriticalSectionDelay + mServer.ProcessingDelay + 1000);
						else
							mTokenHandlerServerSocketPort.setSoTimeout(mServer.ProcessingDelay + 1000);
						///////////////////////////
	
						String rcvd = "";
						try {
							
							rcvd = ReadFromSocket(mTokenHandlerServerSocketPort.accept()); //blocking call (this time will block MAX FOR 6000 ms)	
						
						} catch (SocketTimeoutException e)
						{
							System.out.println("Detected that Right Neighbor must have died between t=0 and t at " + System.currentTimeMillis());
							
							//set right = right_right
							mServer.setmRightDeptModule(mServer.getmRight_RightDeptModule());
							
							//Wait for fix.......
							synchronized (this)
							{
								System.out.println("fixing ring...");
								while (mServer.getmRight_RightDeptModule().mName == null)
								{
									try {
										wait();
									} catch (InterruptedException e1) {}
								}
								System.out.println("ring fixed at " + System.currentTimeMillis());
							}
							
							//after fix.........
							//pass token on to NEW right instead
							//do not care about "RECVD"....2 Broken in a row (at the same time) can't happen
							if(!mServer.getmLeftDeptModule().mName.equals(mServer.getmCurrDeptModule().mName)	&&
									 !mServer.getmRightDeptModule().mName.equals(mServer.getmCurrDeptModule().mName))
								WriteToPort("TOKEN", mServer.getmRightDeptModule());
							System.out.println("*******************");		
						}
						if (rcvd.equals("RCVD_TOKEN"))
						{	
							//cool man.
							System.out.println("*******************");
						}
					}
					if(!mServer.getmLeftDeptModule().mName.equals(mServer.getmCurrDeptModule().mName)	&&
								 !mServer.getmRightDeptModule().mName.equals(mServer.getmCurrDeptModule().mName))
								 mServer.setmInitialToken(false);	
				}
				
				
				
				//---------------------------WIHOUT TOKEN (ESSENTIALLY THE RIGHT NEIGHBOR)-------------------------------------
				
				
				
				else if (!mServer.getmLeftDeptModule().mName.equals(mServer.getmCurrDeptModule().mName)	&&
						 !mServer.getmRightDeptModule().mName.equals(mServer.getmCurrDeptModule().mName))
				{
					mServer.setmBusy(false); 	//free...for reconfiguring.... cuz im just blocked on accept() here........
					System.out.println("free for a bit...");
					
					//receive tokens from YOUR LEFT NEIGHBOR					
					String value = ReadFromSocket(mTokenHandlerServerSocketPort.accept());	//block forever until you get a ....."ST$5$#left_name#left_num"
	
					mServer.setmBusy(true);	//back to work
					
					if (value.equals("TOKEN"))
					{
						//cool. cool. cool.
						
						//send back to the left "RCVD_TOKEN"
						WriteToPort("RCVD_TOKEN", mServer.getmLeftDeptModule());

						// do all the stuff from the first case
						mServer.setmInitialToken(true);
					}
					if (!value.equals("") && !value.equals("TOKEN") && value.contains("ST"))
					{
						//update left with info provided
						String[] temp = value.split("#");
						mServer.setmLeftDeptModule(new DeptModuleInfo(temp[1], Integer.parseInt(temp[2])));
						
						//send back to the left "OK#right_name#right_num"
						WriteToPort("OK" + mServer.getmRightDeptModule().toString(), mServer.getmLeftDeptModule());
						
						//-----TODO--------
						String[] timeSplit = value.split(":");
						//Start your timer for appropriate seconds MAX to receive token from A 
						System.out.println("Going to wait for "+Integer.parseInt(timeSplit[1])+" seconds  starting " + System.currentTimeMillis());
						//mTokenHandlerServerSocketPort.setSoTimeout(6000);
						//if(timeSplit[0].equals("ST"))
						mTokenHandlerServerSocketPort.setSoTimeout(Integer.parseInt(timeSplit[1]));
	
						String token_val = "";
						try {
	
							token_val = ReadFromSocket(mTokenHandlerServerSocketPort.accept()); //blocking call (this time will block MAX FOR 1000 ms)	
	
						} catch (SocketTimeoutException e)
						{
							System.out.println("A must have died between t=0 and t="+Integer.parseInt(timeSplit[1]));
	
							//We don't care about this....
	
							//A died without sending me the token
							System.out.println("Regenerating the token...Regenerated!");
							mServer.setmInitialToken(true);
						}
	
						if (token_val.equals("TOKEN"))
						{
							//cool. cool. cool.
	
							//send back to the left "RCVD_TOKEN"
							WriteToPort("RCVD_TOKEN", mServer.getmLeftDeptModule());
	
							// do all the stuff from the first case
							mServer.setmInitialToken(true);
						}
					}
				}
			} catch (IOException e) {
				//System.out.println("Could not listen on port " + mTokenHandlerServerSocketPort.getLocalPort());
				//e.printStackTrace();
				//continue;
				//System.exit(0);
			}
		}
	}
	
	public void WriteToPort(String stringToWrite, DeptModuleInfo dept)
	{
    	try {
    		int port = dept.mTokenSocketPortNumber;
    		if(stringToWrite.contains("CON_"))
    			port = dept.mTokenSocketPortNumber + 1;
    			
    		Socket client = new Socket(server_addr, port);
			PrintWriter Writer = new PrintWriter(client.getOutputStream(), true);
			Writer.println(stringToWrite);
			
			System.out.println(mServer.getmCurrDeptModule().mName + " > " + stringToWrite + " > " + dept.mName + " at " + System.currentTimeMillis());
			
			client.close();
			Writer.close();
    		
    	} catch (IOException e) 
    	{ 
    		//e.printStackTrace();
    	}
	}
	
	public String ReadFromSocket(Socket socket)
	{
		String socketValue = "";
		try {
			
			BufferedReader Reader = new BufferedReader( new InputStreamReader(socket.getInputStream()) );
			socketValue = Reader.readLine();
			
			socket.close();
			Reader.close();
			
		} catch (IOException e) 
		{
			//e.printStackTrace();
		}
		
		System.out.println(mServer.getmCurrDeptModule().mName + " got " + socketValue + " at " + System.currentTimeMillis());
		
		return socketValue;
	}

}